// CopyLocationShl.cpp : Implementation of CCopyLocationShl
#include "stdafx.h"
#include <atlconv.h>  // for ATL string conversion macros

#include "CopyLocation.h"
#include "CopyLocationShl.h"
#include "CommandsRegistry.h"

/////////////////////////////////////////////////////////////////////////////
// CCopyLocationShl

CCopyLocationShl::CCopyLocationShl()
{
	m_hCopyBmp = LoadBitmap(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDB_COPY));
}


HRESULT CCopyLocationShl::Initialize(
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT  pDataObj,
    HKEY          hProgID
	)
{
	UNREFERENCED_PARAMETER(pidlFolder);
	UNREFERENCED_PARAMETER(hProgID);

	FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
	STGMEDIUM stg = { TYMED_HGLOBAL };
	HDROP     hDrop = NULL;

    // Look for CF_HDROP data in the data object
    if (FAILED(pDataObj->GetData(&fmt, &stg)))
    {
        // Nope! Return an "invalid argument" error back to Explorer
        return E_INVALIDARG;
    }

    // Get a pointer to the actual data
    hDrop = (HDROP)GlobalLock(stg.hGlobal);
    if (NULL == hDrop)
    {
        GlobalUnlock(stg.hGlobal);
        ReleaseStgMedium(&stg);
        return E_INVALIDARG;
    }

    // Sanity check	- make sure there is at least one filename
    UINT uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
    if (0 == uNumFiles)
    {
        GlobalUnlock(stg.hGlobal);
        ReleaseStgMedium(&stg);
        return E_INVALIDARG;
    }

    TCHAR szFile[MAX_PATH];
    for (UINT uFile = 0 ; uFile < uNumFiles ; uFile++)
    {
        // Get the next filename.
        if (DragQueryFile(hDrop, uFile, szFile, MAX_PATH))
        {
            // Add the file name to our list of files (m_lsFiles).
            m_lsFiles.push_back(szFile);
        }
    }

    GlobalUnlock(stg.hGlobal);
    ReleaseStgMedium(&stg);
    // If we found any files we can work with, return S_OK. Otherwise, return
    // E_INVALIDARG so we don't get called again for this right-click operation.
    return (m_lsFiles.size() > 0) ? S_OK : E_INVALIDARG;
}


HRESULT CCopyLocationShl::QueryContextMenu(
    HMENU hmenu,
    UINT  uMenuIndex, 
    UINT  uidFirstCmd,
    UINT  uidLastCmd,
    UINT  uFlags
	)
{
	UNREFERENCED_PARAMETER(uidLastCmd);

	// If the flags include CMF_DEFAULTONLY then we shouldn't do anything
    if (uFlags & CMF_DEFAULTONLY)
	{
        return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
	}

    HMENU subMenu = CreatePopupMenu();

    DWORD cmdIndex = 0;
    for (CommandRegistry::const_iterator pCmd = CommandRegistry::instance().begin(), end = CommandRegistry::instance().end(); pCmd != end; ++pCmd)
    {
        InsertMenu(subMenu, cmdIndex, MF_STRING | MF_BYPOSITION, uidFirstCmd, (*pCmd)->GetMenuItem().c_str());
        cmdIndex++;
        uidFirstCmd++;
    }

    InsertMenu(hmenu, uMenuIndex, MF_BYPOSITION | MF_POPUP, (UINT_PTR)subMenu, _T("CopyPath"));
    if (NULL != m_hCopyBmp)
    {
        SetMenuItemBitmaps(hmenu, uMenuIndex, MF_BYPOSITION, m_hCopyBmp, NULL);
    }


    return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, cmdIndex + 1);
}


HRESULT CCopyLocationShl::GetCommandString(
    UINT_PTR  idCmd,
    UINT  uFlags,
    UINT* pwReserved,
    LPSTR pszName,
    UINT  cchMax
	)
{
	UNREFERENCED_PARAMETER(pwReserved);

    USES_CONVERSION;

    // If Explorer is asking for a help string, copy our string into the
    // supplied buffer
    if (uFlags & GCS_HELPTEXT)
	{
        if (idCmd >= CommandRegistry::instance().size())
            return E_INVALIDARG;

        tchar_string prompt = CommandRegistry::instance()[idCmd]->GetDescription();
        if (uFlags & GCS_UNICODE)
		{
            // We need to cast pszName to a Unicode string, and then use the
            // Unicode string copy API
            lstrcpynW((LPWSTR)pszName, T2CW(prompt.c_str()), cchMax);
		}
        else
		{
            // Use the ANSI string copy API to return the help string
            lstrcpynA(pszName, T2CA(prompt.c_str()), cchMax);
		}

        return S_OK;
	}

    return E_INVALIDARG;
}


void SetClipboardText(HWND hwnd, tchar_string const& text)
{
    size_t size = (text.length() + 1) * sizeof(TCHAR);
    if (HGLOBAL hCom = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, size))
    {
        // MSDN SetClipboardData: The application can read the data, but must not free the handle or leave it locked until the CloseClipboard function is called. 
        if (OpenClipboard(hwnd))
        {
            EmptyClipboard();

            if (LPVOID pCom = GlobalLock(hCom))
            {
                CopyMemory(pCom, text.c_str(), size);
                GlobalUnlock(hCom);
#ifdef UNICODE
                SetClipboardData(CF_UNICODETEXT, hCom);
#else
                SetClipboardData(CF_TEXT, hCom);
#endif // UNICODE
            }
            CloseClipboard();
        }
    }
}

tchar_string SerializeNamesList(ICommand& extractor, string_list const& files)
{
    tchar_string result;
    // accumulate all the extracted filenames separating them with a line break
    for (string_list::const_iterator i = files.begin(), end = files.end(); i != end; ++i)
    {
        result += extractor.GetContent(*i) + _T("\r\n");
    }

    // if the buffer is not empty, trim off the last line break
    if (result.length() > 2)
        result = result.substr(0, result.length()-2);

    return result;
}

HRESULT CCopyLocationShl::InvokeCommand(LPCMINVOKECOMMANDINFO pCmdInfo)
{
    // If lpVerb really points to a string, ignore this function call and bail out
    if (0 != HIWORD(pCmdInfo->lpVerb))
        return E_INVALIDARG;

    // Get the command index - the only valid commands are 0 and 1
	WORD wCmd = LOWORD(pCmdInfo->lpVerb);
	if (wCmd >= CommandRegistry::instance().size())
		return E_INVALIDARG;

    ICommand& nameExtractor = *CommandRegistry::instance()[wCmd];
    
	tchar_string clipBuffer = SerializeNamesList(nameExtractor, m_lsFiles);
      
    SetClipboardText(pCmdInfo->hwnd, clipBuffer);

	return S_OK;
}